<?php

/**
 *没学过cli简陋的cli模式
 */
namespace app;


if(PHP_SAPI=="cli"){
    require "CORE/Extend/Vendor/Workerman/Autoloader.php";
    $argv = $_SERVER['argv']?$_SERVER['argv']:[];
    $argc = $_SERVER['argc'];

    $b=new \app\BrowserSync();
    $b->execute($argv);exit;
}
use Workerman\Lib\Timer;
use Workerman\Worker;

/**
 * 浏览器自动刷新
 * Class BrowserSync
 * @package app\console
 */
class BrowserSync
{

    /**
     * socket端口
     * @var int
     */
    private $socket_port = 8008;

    /**
     * 输出
     * @var null
     */
    private $output_object = null;

    /**
     * 输入
     * @var null
     */
    private $input_object = null;

    /**
     * pid file
     * @var string
     */
    private $worker_man_pid_file = 'browser_sync.pid';

    /**
     * log file
     * @var string
     */
    private $worker_man_log_file = 'browser_sync.log';

    /**
     * port file
     * @var string
     */
    private $worker_man_port_file = 'browser_sync.port';

    /**
     * 实例对象
     * @var null
     */
    private static $instance_instance = null;

    /**
     * 监听文件结果
     * @var array
     */
    private $scan_files = [];

    /**
     * 实例对象
     * @return BrowserSync|null
     */

    /**
     * 实例对象
     * @return BrowserSync|null
     */
    public static function instance()
    {
        if (self::$instance_instance == null) {
            self::$instance_instance = new self();
        }
        return self::$instance_instance;
    }



    /**
     * 执行
     * @param Input $input
     * @param Output $output
     * @return bool
     */
    public function execute(array $argv)
    {

        set_time_limit(0);
        $help="\n#############################################\n###########       TonicGB           ##########\n###########       使用方式           ##########\n##############################################\n例子：php index.php browser_sync start css;html;php  App/Tpl/Home/default/Index/ \n注意监听多个后缀使用分号分开，也可以指定多个文件目录一样使用注意分号。\n 启动命令：\n start/启动，\n start -d/启动（守护进程），\n status/状态, \n restart/重启， \n reload/平滑重启， \n stop/停止 \n 注意：在运行过程中如果想暂停同步添加?browser_sync=0 开始?browser_sync=1";

        if(in_array("--help",$argv)) {print_r(iconv('UTF-8',"GBK//IGNORE",$help));exit;}
        $waring="\n####您可以输入php index.php --help 来查看使用情况###### \n \n您输入的命令错误，command error，\n 请输入如下命令：\n start/启动，\n start -d/启动（守护进程），\n status/状态, \n restart/重启， \n reload/平滑重启， \n stop/停止 \n ";
        if(count($argv)==1){
            print_r(iconv('UTF-8',"GBK//IGNORE",$waring));
            exit;
        }
        foreach ($argv as $k=>$v){
            switch ($k){
                case 1: $arr['cname']=$v; break;
                case 2: $arr['command']=$v;break;
                case 3: $arr['ext']=$v; break;
                case 4: $arr['dir']=$v; break;
            }
        }


        $this->input_object=$arr;

        return $this->syncClient();
    }

    /**
     * 启动workerman,同步客户端
     */
    private function syncClient()
    {

        $command = trim($this->input_object['command']);
        $command = $this->spaceManyToOne($command);

        if (!in_array($command, ['start', 'start-d', 'stop', 'restart', 'reload', 'status'])) {
            $waring2="\n####您可以输入php index.php --help 来查看使用情况###### \n \n您输入的命令错误，command error，\n 请输入如下命令：\n start/启动，\n start -d/启动（守护进程），\n status/状态, \n restart/重启， \n reload/平滑重启， \n stop/停止 \n";
            print_r(iconv('UTF-8',"GBK//IGNORE",$waring2));

            exit;
        }

        if ($command == 'start-d') {
            $GLOBALS['argv'][1] = 'start';
            $GLOBALS['argv'][2] = '-d';
        } else {
            $GLOBALS['argv'][1] = $command;
        }
        $worker = new Worker(sprintf('websocket://0.0.0.0:%s', $this->socket_port));
        //进程名称
        $worker->name ="Tonicgb";
        $worker->id=0;
        //设置进程id文件地址
        $worker::$pidFile = $this->worker_man_pid_file;
        //设置日志文件
        $worker::$logFile = $this->worker_man_log_file;
        $worker->onWorkerStart = function ($worker) {
            //定时器定时监听
            Timer::add(0.5, function () use ($worker) {
                //记录端口号，用于生成js自动刷新代码
                $this->port($this->socket_port);
                if ($this->fileIsModify()) {

                    // 遍历当前进程所有的客户端连接，发送当前服务器的时间
                    foreach ($worker->connections as $connection_each) {
                       // file_put_contents("text.txt",$worker->id."is over \n",FILE_APPEND);
                        $connection_each->send('fresh');
                    }
                }
            });
        };
        //worker进程终止时发生,回收worker进程申请的资源，此函数能够处理做一些清理工作，例如保存一些重要数据等
        $worker->onWorkerStop = function ($worker) {
            //删除端口号
            $this->port(-1);
            //刷新页面
            foreach ($worker->connections as $connection_each) {
                file_put_contents("text.txt",$worker->id."is over \n",FILE_APPEND);
            }
        };
        //当客户端连接与Workerman断开时触发的回调函数。不管连接是如何断开的，只要断开就会触发onClose。每个连接只会触发一次onClose。
        $worker->onClose=function($worker){

            echo $worker->id."connection closed\n";

        };
        // 运行worker
        Worker::runAll();
        return true;
    }

    /**
     * 功能：拼接socket 监听js
     * 说明：
     * 1.在url地址拼接browser_sync=0 终止监听
     *
     * 2.在通过cookie没有设置过值初次设置设置，没有设置过值
     * @return string
     */
    public function getJsContent()
    {
        $port = $this->socket_port;
        if (empty($port)) {
            return '';
        }


        return $_REQUEST['browser_sync'] === 1? sprintf('<script type="text/javascript">
    var ws = new WebSocket(\'ws://%s:%s\');
    ws.onclose = function(){
       alert("后台进程由于某种原因,已被关掉了");
    };
    ws.onmessage = function(e) {
        console.log(e);
       if(e.data=="fresh")
	    window.location.reload();  
	  
    };
</script>',$_SERVER['HTTP_HOST'], $port) : '';
    }
    /**
     * 多个空格转换为一个空格
     * @param $string
     * @param string $separator
     * @return string
     */
    private function spaceManyToOne($string, $separator = ' ')
    {
        return trim(preg_replace('/[\s]+/', $separator, $string));
    }


    /**
     * 获取监听的文件是否被修改
     * @return bool
     */
    private function fileIsModify()
    {

        //清除缓存
        clearstatcache();
        $last_scan_files = $this->scan_files;
        $files_types = $this->input_object['ext'];
        if (empty($files_types)) {
           print_r('<error>请输入监听文件files的类型</error>');
            return false;
        }

        $files_types = explode(';', trim(trim(str_replace('；', ';', $files_types)), ';'));

        $dirs = $this->input_object['dir'];
        $root_path = __DIR__;

        $dirs = explode(';', trim(trim(str_replace('；', ';', $dirs)), ';'));
        $files = [];
        foreach ($dirs as $dir) {
            if (empty($dir) || !is_dir($root_path . '/' . $dir)) {
                continue;
            }
            $files = array_merge($files, $this->dirGetFiles($root_path . '/' . $dir, $files_types));

        }
        $scan_files = [];
        //文件内容改变，md5_file计算时间较长，以文件最后修改的时间为判断依据
        if (!empty($files)) {
            foreach ($files as $file) {
                $scan_files[$file] = filemtime($file);

            }
        }
        //判断文件是否改变
        $has_modify = false;
        //文件不存在
        if (empty($last_scan_files)) {
            foreach ($scan_files as $file => $modify_time) {
                if (!array_key_exists($file, $last_scan_files) || $last_scan_files[$file] != $modify_time) {
                    $last_scan_files[$file]=$modify_time;
                    echo sprintf("[%s] %s\n", date('Y-m-d H:i:s'), $file);
                    $has_modify = true;
                    break;
                }
            }}else{
                foreach ($scan_files as $file => $modify_time) {
                    if (!array_key_exists($file, $last_scan_files) || $last_scan_files[$file] != $modify_time) {
                        $last_scan_files[$file]=$modify_time;
                        echo sprintf("[%s] %s\n", date('Y-m-d H:i:s'), $file);
                        $has_modify = true;
                        break;
                    }
            }
        }
        //设置新文件记录
        $this->scan_files = $scan_files;
        return $has_modify;
    }


    /**
     * 获取文件夹下面的所有文件
     * @param $dir 文件夹目录绝对地址
     * @param array $file_types :文件类型array('pdf', 'doc')
     * @param array $ignore_dir_or_file : 忽略的文件或文件夹
     * @return array
     */
    private function dirGetFiles($dir, $file_types = array(), $ignore_dir_or_file = [])
    {
        foreach (['.', '..'] as $each) {
            if (!in_array($each, $ignore_dir_or_file)) {
                $ignore_dir_or_file[] = $each;
            }
        }
        $data = array();
        if (is_dir($dir)) {
            $files = scandir($dir);
            foreach ($files as $file) {
                if (in_array($file, $ignore_dir_or_file)) {
                    continue;
                }
                if (is_dir($dir . '/' . $file)) {
                    $data = array_merge($data, $this->dirGetFiles($dir . '/' . $file, $file_types, $ignore_dir_or_file));
                } else {
                    if (empty($file_types)) {
                        $data[] = $dir . '/' . $file;
                    } else {
                        //判断类型
                        if (in_array($this->getSuffix($file), $file_types)) {
                            $data[] = $dir . '/' . $file;
                        }
                    }
                }
            }
        } else if (is_file($dir)) {
            if (empty($file_types)) {
                if (!in_array($dir, $ignore_dir_or_file)) {
                    $data[] = $dir;
                }
            } else {
                //判断类型
                if (in_array($this->getSuffix($dir), $file_types) && !in_array($dir, $ignore_dir_or_file)) {
                    $data[] = $dir;
                }
            }
        }
        return $data;
    }
    /**
     * 获取文件后缀名
     * @param $file
     * @return mixed
     */
    private function getSuffix($file)
    {
        return isset(pathinfo($file)['extension']) ? strtolower(pathinfo($file)['extension']) : '';
    }

    /**
     * 端口号
     * @param int $port 0/获取， < 0 / 删除，> 0 / 设置
     * @return bool|mixed
     */
    public function port($port = 0)
    {

        //清除缓存
        clearstatcache();
        if (empty($port)) {
            if (is_file($this->worker_man_port_file)) {
                //该文件修改时间小于安全时间，则判断当前监听无效
                if (filemtime($this->worker_man_port_file) + 2 < time()) {
                    //清除无效文件
                    foreach ([$this->worker_man_port_file, $this->worker_man_pid_file, $this->worker_man_log_file] as $file) {
                        if (is_file($file)) {
                            unlink($file);
                        }
                    }
                    return null;
                } else {
                    return file_get_contents($this->worker_man_port_file);
                }
            } else {
                return null;
            }
        } else if ($port > 0) {
            return file_put_contents($this->worker_man_port_file, $port);
        } else {
            return unlink($this->worker_man_port_file);
        }
    }


}
